﻿using Candy.Web.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.DomainServices.Client;
using System.Threading.Tasks;

namespace Candy.Model
{
    public class CandySlDataService : IDataService
    {
        public EntitySet<ProjectDto> Projects
        {
            get { return DataContext != null ? DataContext.ProjectDtos : null; }
        }

        public EntitySet<FamilyDto> Families
        {
            get { return DataContext != null ? DataContext.FamilyDtos : null; }
        }

        public EntitySet<GeneDto> Genes
        {
            get { return DataContext != null ? DataContext.GeneDtos : null; }
        }

        public EntitySet<GenotypeDto> Genotypes
        {
            get { return DataContext != null ? DataContext.GenotypeDtos : null; }
        }

        public EntitySet<DatabaseDto> Databases
        {
            get { return DataContext != null ? DataContext.DatabaseDtos : null; }
        }

        public EntitySet<GeneAnnotationDto> GeneAnnotations
        {
            get { return DataContext != null ? DataContext.GeneAnnotationDtos : null; }
        }

        public EntitySet<PhylogenyDto> Phylogenies
        {
            get { return DataContext != null ? DataContext.PhylogenyDtos : null; }
        }

        public Web.CandyDataContext DataContext
        {
            get;
            private set;
        }

        public ProjectDto LoadedProject
        {
            get;
            private set;
        }

        public CandySlDataService()
        {
            DataContext = new Web.CandyDataContext();
            
        }

        public void LoadProjectListAsync(Action<CandyLoadOperation<ProjectDto>> callback)
        {
            var query = DataContext.GetProjectsQuery();
            DataContext.Load<ProjectDto>(query, op =>
            {
                if (callback != null)
                {
                    CandyLoadOperation<ProjectDto> cop = new CandyLoadOperation<ProjectDto>(op);
                    callback(cop);
                }

            }, null);
        }

        public void RefreshFamiliesAsync(Action<CandyLoadOperation<FamilyDto>> callback)
        {
            if (LoadedProject == null)
                throw new InvalidOperationException("No project initialized.");

            var query = DataContext.GetFamiliesQuery(LoadedProject.Id, false);
            DataContext.Load<FamilyDto>(query, LoadBehavior.MergeIntoCurrent, op =>
            {
                if (callback != null)
                {
                    CandyLoadOperation<FamilyDto> cop = new CandyLoadOperation<FamilyDto>(op);
                    callback(cop);
                }

            }, null);
        }

        public void CreateJobAsync(Action<CandyInvokeOperation<List<BlastJobDto>>> callback, BlastJobDefinitionDto val)
        {
            DataContext.CreateJob(LoadedProject.Id, val, op =>
            {
                if (callback != null)
                {
                    CandyInvokeOperation<List<BlastJobDto>> cop = new CandyInvokeOperation<List<BlastJobDto>>(op);
                    callback(cop);
                }
            }, null);

        }

        public void CreateJobsAsync(Action<CandyInvokeOperation<List<BlastJobDto>>> callback, List<BlastJobDefinitionDto> val)
        {
            DataContext.CreateJobs(LoadedProject.Id, val, op =>
            {
                if (callback != null)
                {
                    CandyInvokeOperation<List<BlastJobDto>> cop = new CandyInvokeOperation<List<BlastJobDto>>(op);
                    callback(cop);
                }
            }, null);

        }

        public void LoadGenesAsync(Action<CandyLoadOperation<GeneDto>> callback)
        {
            if (LoadedProject == null)
                throw new InvalidOperationException("No project initialized.");

            var query = DataContext.GetGenesQuery(LoadedProject.Id);
            DataContext.Load<GeneDto>(query, op =>
            {
                if (callback != null)
                {
                    CandyLoadOperation<GeneDto> cop = new CandyLoadOperation<GeneDto>(op);
                    callback(cop);
                }

            }, null);
        }

        public void LoadJobsAsync(Action<CandyLoadOperation<BlastJobDto>> callback)
        {
            if (LoadedProject == null)
                throw new InvalidOperationException("No project initialized.");
            var query = DataContext.GetJobsQuery(LoadedProject.Id);
            DataContext.Load<BlastJobDto>(query, op =>
           {
               if (callback != null)
               {
                   CandyLoadOperation<BlastJobDto> cop = new CandyLoadOperation<BlastJobDto>(op);
                   callback(cop);
               }

           }, null);
        }

        public void RefreshJobsAsync(Action<CandyLoadOperation<BlastJobDto>> callback, IEnumerable<BlastJobDto> jobs)
        {
            if (LoadedProject == null)
                throw new InvalidOperationException("No project initialized.");
            List<int> jobsToRefresh = new List<int>();
            DateTime youngestJob = jobs.Max(x => x.DateTime);
            jobsToRefresh.AddRange(jobs.Where(x => x.Status == RequestStatus.PENDING || x.Status == RequestStatus.RUNNING).Select(x => x.Id));

            var query = DataContext.RefreshJobsQuery(WebContext.Current.User.UserID, LoadedProject.Id, jobsToRefresh.Distinct(), youngestJob);
            DataContext.Load<BlastJobDto>(query, LoadBehavior.MergeIntoCurrent, op =>
           {
               if (callback != null)
               {
                   CandyLoadOperation<BlastJobDto> cop = new CandyLoadOperation<BlastJobDto>(op);
                   callback(cop);
               }

           }, null);
        }



        public void LoadBlastHitsAsync(int jobId, Action<CandyLoadOperation<HitResultDto>> callback)
        {
            if (LoadedProject == null)
                throw new InvalidOperationException("No project initialized.");

            var q = DataContext.GetBlastResultsQuery(LoadedProject.Name, jobId);
            DataContext.Load<HitResultDto>(q, op =>
            {
                if (callback != null)
                {
                    CandyLoadOperation<HitResultDto> cop = new CandyLoadOperation<HitResultDto>(op);
                    callback(cop);
                }

            }, null);
        }

        public void RefreshGenotypes(Action<CandyInvokeOperation> callback)
        {
            var genotypeQuery = DataContext.GetGenotypesQuery(LoadedProject.Id);
            DataContext.Load<GenotypeDto>(genotypeQuery, LoadBehavior.MergeIntoCurrent, op =>
            {
                if (callback != null)
                {
                    CandyInvokeOperation<bool> ret = new CandyInvokeOperation<bool>(op.Error);
                    callback(ret);
                    if (ret.IsErrorHandled)
                        op.MarkErrorAsHandled();
                }
            }, null);
        }

        System.Threading.AutoResetEvent _initEvent = new System.Threading.AutoResetEvent(false);
        public void Initialize(ProjectDto project, Action<CandyInvokeOperation<bool>> callback = null)
        {
            Task t = new Task(() =>
                {
                    if (project == null)
                        throw new ArgumentException("Project can't be null");
                    LoadedProject = project;


                    bool keepLoading = true;
                    var query = DataContext.GetFamiliesQuery(LoadedProject.Id, true);
                    DataContext.Load<FamilyDto>(query, op =>
                    {
                        if (op.HasError)
                        {
                            keepLoading = false;
                            if (callback != null)
                            {
                                CandyInvokeOperation<bool> ret = new CandyInvokeOperation<bool>(op.Error);
                                callback(ret);
                                if (ret.IsErrorHandled)
                                    op.MarkErrorAsHandled();

                            }
                            GalaSoft.MvvmLight.Threading.DispatcherHelper.CheckBeginInvokeOnUI(() =>
                                {
                                    System.Windows.MessageBox.Show("Failed project: " + project.Name);
                                    System.Windows.MessageBox.Show(op.Error.Message);
                                });
                        }
                        _initEvent.Set();
                        //else
                        //{
                        //    if (callback != null)
                        //    {
                        //        CandyInvokeOperation<bool> ret = new CandyInvokeOperation<bool>(true);
                        //        callback(ret);

                        //    }
                        //    if (ProjectInitialized != null)
                        //        ProjectInitialized(this, EventArgs.Empty);
                        //}
                    }, null);
                    _initEvent.WaitOne();
                    if (!keepLoading)
                        return;

                    var genotypeQuery = DataContext.GetGenotypesQuery(LoadedProject.Id);
                    DataContext.Load<GenotypeDto>(genotypeQuery, op =>
                       {
                           if (op.HasError)
                           {
                               keepLoading = false;
                               if (callback != null)
                               {
                                   CandyInvokeOperation<bool> ret = new CandyInvokeOperation<bool>(op.Error);
                                   callback(ret);
                                   if (ret.IsErrorHandled)
                                       op.MarkErrorAsHandled();
                               }
                           }
                           _initEvent.Set();
                       }, null);

                    _initEvent.WaitOne();

                    if (!keepLoading)
                        return;

                    var databaseQuery = DataContext.GetDatabasesQuery(LoadedProject.Id);
                    DataContext.Load<DatabaseDto>(databaseQuery, op =>
                    {
                        if (op.HasError)
                        {
                            if (callback != null)
                            {
                                CandyInvokeOperation<bool> ret = new CandyInvokeOperation<bool>(op.Error);
                                callback(ret);
                                if (ret.IsErrorHandled)
                                    op.MarkErrorAsHandled();
                            }
                        }
                        _initEvent.Set();
                    }, null);

                    _initEvent.WaitOne();
                    if (callback != null)
                    {
                        CandyInvokeOperation<bool> ret = new CandyInvokeOperation<bool>(true);
                        callback(ret);

                    }
                    if (ProjectInitialized != null)
                        ProjectInitialized(this, EventArgs.Empty);
                });
            t.Start();
        }

        public void DeInitialize()
        {
            //TODO: reset datacontext
            LoadedProject = null;
            if (ProjectDeInitialized != null)
                ProjectDeInitialized(this, EventArgs.Empty);
        }

        public event EventHandler ProjectInitialized;

        public event EventHandler ProjectDeInitialized;

        public void FamilyExistsAsync(int projectId, int selfFamilyId, string familyName, Action<CandyInvokeOperation<bool>> callback)
        {
            DataContext.FamilyExists(projectId, selfFamilyId, familyName, op =>
                {
                    CandyInvokeOperation<bool> cop = new CandyInvokeOperation<bool>(op);
                    callback(cop);
                }, null);
        }

        public void ProjectExistsAsync(string projectName, Action<CandyInvokeOperation<bool>> callback)
        {
            DataContext.ProjectExists(projectName, op =>
            {
                CandyInvokeOperation<bool> cop = new CandyInvokeOperation<bool>(op);
                callback(cop);
            }, null);
        }

        public void SubmitAsync(Action<CandySubmitOperation> callback)
        {
            DataContext.SubmitChanges(op =>
                {
                    if (callback != null)
                        callback(new CandySubmitOperation(op));
                }, null);
        }

        public void GeneIdentifierExistsAsync(int projectId, int selfGeneId, string geneIdentifier, Action<CandyInvokeOperation<bool>> callback)
        {
            DataContext.GeneIdentifierExists(projectId, selfGeneId, geneIdentifier, op =>
            {
                CandyInvokeOperation<bool> cop = new CandyInvokeOperation<bool>(op);
                callback(cop);
            }, null);
        }

        public void GeneNameExistsAsync(int projectId, int selfGeneId, string geneName, Action<CandyInvokeOperation<bool>> callback)
        {
            DataContext.GeneNameExists(projectId, selfGeneId, geneName, op =>
            {
                CandyInvokeOperation<bool> cop = new CandyInvokeOperation<bool>(op);
                callback(cop);
            }, null);
        }

        public void LoadGeneAnnotaionAsync(int geneId, Action<CandyLoadOperation<GeneAnnotationDto>> callback)
        {
            if (LoadedProject == null)
                throw new InvalidOperationException("No project initialized.");
            var query = DataContext.GetAnnotationsQuery(LoadedProject.Id, geneId);
            DataContext.Load<GeneAnnotationDto>(query, op =>
            {
                if (callback != null)
                {
                    CandyLoadOperation<GeneAnnotationDto> cop = new CandyLoadOperation<GeneAnnotationDto>(op);
                    callback(cop);
                }

            }, null);
        }

        public void LoadPhylogeniesAsync(int familyId, Action<CandyLoadOperation<PhylogenyDto>> callback)
        {
            if (LoadedProject == null)
                throw new InvalidOperationException("No project initialized.");
            var query = DataContext.GetPhylogeniesQuery(LoadedProject.Id, familyId);
            DataContext.Load<PhylogenyDto>(query, op =>
            {
                if (callback != null)
                {
                    CandyLoadOperation<PhylogenyDto> cop = new CandyLoadOperation<PhylogenyDto>(op);
                    callback(cop);
                }

            }, null);
        }
    }
}
